package room

import (
	"core/entity"
	"core/remote"
	"core/xyError"
	"game/component/base"
	"game/component/proto"
	"game/component/sz"
	"game/models"
	"sync"
	"time"
)

type Room struct {
	sync.RWMutex
	Id            string
	unionId       int64
	gameRule      *proto.GameRule
	users         map[string]*proto.RoomUserInfo
	RoomCreator   *proto.RoomCreator
	gameFrame     base.GameFrame
	kickSchedules map[string]*time.Timer // 踢出
	union         base.UnionBase
	roomDismissed bool // 房间是否解散
	gameStarted   bool // 是否开始游戏
}

func (r *Room) UserReady(uid string, session *remote.Session) {
	r.UserReady(uid, session)
}

func (r *Room) EndGame(session *remote.Session) {
	r.gameStarted = false
	for i := range r.users {
		r.users[i].UserStatus = proto.None
	}
}

func (r *Room) UserEntryRoom(session *remote.Session, user *entity.User) *xyError.Error {
	creator := &proto.RoomCreator{
		Uid: user.Uid,
	}
	if r.unionId == 1 {
		creator.CreatorType = proto.UserCreatorType
	} else {
		creator.CreatorType = proto.UnionCreatorType
	}
	r.RoomCreator = creator
	// 座次号
	chairId := r.genEmptyChairId()
	r.users[user.Uid] = proto.ToRoomUserInfo(user, chairId)
	// 1. 将房间号推送给客户端
	r.UpdateUserInfoRoomPush(session, user.Uid)
	// 2. 游戏类型推送
	r.SelfEntryRoomPush(session, user.Uid)
	// 3. 将房间号放到 Session 中
	session.Put("roomId", r.Id)
	// 4. 告诉其他人，此用户进入房间了
	r.OtherUserEntryRoomPush(session, user.Uid)
	// 5. 定时踢出用户
	go r.addKickScheduleEvent(session, user.Uid)
	return nil
}

// OtherUserEntryRoomPush 通知其它用户该用户进入房间
func (r *Room) OtherUserEntryRoomPush(session *remote.Session, uid string) {
	// 获取房间内所有用户并进行推送
	otherUser := make([]string, len(r.users)-1)
	userInfo := new(proto.RoomUserInfo)
	for _, user := range r.users {
		if user.UserInfo.Uid != uid {
			otherUser = append(otherUser, user.UserInfo.Uid)
		} else {
			userInfo = user
		}
	}
	pushMsg := map[string]any{
		"type": proto.OtherUserEntryRoomPush,
		"data": map[string]any{
			"roomUserInfo": userInfo,
		},
		"pushRouter": "RoomMessagePush",
	}
	// 将消息通过 nats 推送给 connector，connector 再主动推送给客户端
	session.Push(otherUser, pushMsg, "ServerMessagePush")
}

func (r *Room) UpdateUserInfoRoomPush(session *remote.Session, uid string) {
	pushMsg := map[string]any{
		"roomId":     r.Id,
		"pushRouter": "UpdateUserInfoPush",
	}
	// 将消息通过 nats 推送给 connector，connector 再主动推送给客户端
	session.Push([]string{uid}, pushMsg, "ServerMessagePush")
}

func (r *Room) SelfEntryRoomPush(session *remote.Session, uid string) {
	pushMsg := map[string]any{
		"gameType":   r.gameRule.GameType,
		"pushRouter": "SelfEntryRoomPush",
	}
	// 将消息通过 nats 推送给 connector，connector 再主动推送给客户端
	session.Push([]string{uid}, pushMsg, "ServerMessagePush")
}

func (r *Room) RoomMessageHandler(session *remote.Session, param *models.RoomMessageRequest) {
	if param.Type == proto.GetRoomSceneInfoNotify {
		r.GetRoomSceneInfoPush(session)
	}
	// 用户准备
	if param.Type == proto.UserReadyNotify {
		r.userReady(session, param)
	}
}

// GetRoomSceneInfoPush 获取房间场景
func (r *Room) GetRoomSceneInfoPush(session *remote.Session) {
	userInfoArr := make([]*proto.RoomUserInfo, 0)
	for _, v := range r.users {
		userInfoArr = append(userInfoArr, v)
	}
	data := map[string]any{
		"type":       proto.GetRoomSceneInfoPush,
		"pushRouter": "RoomMessagePush",
		"data": map[string]any{
			"roomID":          r.Id,
			"roomCreatorInfo": r.RoomCreator,
			"gameRule":        r.gameRule,
			"roomUserInfoArr": userInfoArr,
			"gameData":        r.gameFrame.GetGameData(session),
		},
	}
	session.Push([]string{session.Msg.Uid}, data, "ServerMessagePush")
}

func (r *Room) addKickScheduleEvent(session *remote.Session, uid string) {
	r.Lock()
	defer r.Unlock()
	// 客户端重新加载页面可能会出现重复进入房间，这里先删掉旧值再创建新定时任务
	timer, ok := r.kickSchedules[uid]
	if ok {
		timer.Stop()
		delete(r.kickSchedules, uid)
	}
	r.kickSchedules[uid] = time.AfterFunc(30*time.Second, func() {
		//logs.Debug("kick 定时制定，%s 长时间未准备被踢出", uid)
		/*timer, ok := r.kickSchedules[uid]
		if ok {
			timer.Stop()
		}*/
		// 移除当前用户
		user, ok := r.users[uid]
		if ok && user.UserStatus < proto.Ready {
			r.kickUser(user, session)
			delete(r.kickSchedules, uid)
			// 如果房间没有人了，则需要解散房间
			r.dismissRoom()
		}
	})
}

func (r *Room) kickUser(user *proto.RoomUserInfo, session *remote.Session) {
	pushMsg := map[string]any{
		"roomId":     "",
		"pushRouter": "UpdateUserInfoPush",
	}
	session.Push([]string{user.UserInfo.Uid}, pushMsg, "ServerMessagePush")
	// 通知其他人该用户离开房间
	users := make([]string, 0)
	for _, u := range r.users {
		users = append(users, u.UserInfo.Uid)
	}
	userLeave := map[string]any{
		"pushRouter": "RoomMessagePush",
		"data": map[string]any{
			"roomUserInfo": user,
		},
		"type": proto.UserLeaveRoomPush,
	}
	session.Push(users, userLeave, "ServerMessagePush")
	// 移除用户
	delete(r.users, user.UserInfo.Uid)
}

// 解散房间
func (r *Room) dismissRoom() {
	r.Lock()
	defer r.Unlock()
	if r.roomDismissed {
		return
	}
	r.union.DismissRoom(r.Id)
	// 关闭房间内所有的定时任务
	r.cancelAllScheduler()
	r.roomDismissed = true
}

func (r *Room) cancelAllScheduler() {
	for uid, kick := range r.kickSchedules {
		kick.Stop()
		delete(r.kickSchedules, uid)
	}
}

func (r *Room) userReady(session *remote.Session, param *models.RoomMessageRequest) {
	uid := session.Msg.Uid
	// 1. 关闭定期不准备定时任务
	timer, ok := r.kickSchedules[uid]
	if ok {
		timer.Stop()
		delete(r.kickSchedules, uid)
	}
	// 2. 修改用户状态
	user := r.users[uid]
	user.UserStatus = proto.Ready
	// 3. 推送座次信息
	pushMsg := map[string]any{
		"type": proto.UserReadyPush,
		"data": map[string]any{
			"chairID": user.ChairId,
		},
		"pushRouter": "RoomMessagePush",
	}
	// 3. 推送用户信息
	session.Push(r.GetAllUid(), pushMsg, "ServerMessagePush")
	// 4. 如果用户都准备了，那么开始游戏
	if r.isStartGame() {
		r.startGame(session, user)
	}
}

func (r *Room) JoinRoom(session *remote.Session, user *entity.User) *xyError.Error {
	return r.UserEntryRoom(session, user)
}

// GetAllUid 获取所有用户 UID
func (r *Room) GetAllUid() []string {
	uIds := make([]string, len(r.users))
	for _, user := range r.users {
		uIds = append(uIds, user.UserInfo.Uid)
	}
	return uIds
}

// genEmptyChairId 生成座次号
func (r *Room) genEmptyChairId() int {
	if len(r.users) == 0 {
		return 0
	}
	r.RLock()
	defer r.RUnlock()
	chair := 0
	for _, user := range r.users {
		if user.ChairId == chair {
			// 座次号被占用
			chair++
		}
	}
	return chair
}

// 该房间是否可以开始游戏
func (r *Room) isStartGame() bool {
	var userReadyCount int
	for _, user := range r.users {
		if user.UserStatus == proto.Ready {
			userReadyCount++
		}
	}
	// 房间内准备的人数大于等于最小准备的人数
	if len(r.users) == userReadyCount && userReadyCount >= r.gameRule.MinPlayerCount {
		return true
	}
	return false
}

// 开始游戏
func (r *Room) startGame(session *remote.Session, user *proto.RoomUserInfo) {
	if r.gameStarted {
		return
	}
	r.gameStarted = true
	// 修改用户状态
	for _, user := range r.users {
		user.UserStatus = proto.Playing
	}
	r.gameFrame.StartGame(session, user)
}

func (r *Room) GetUsers() map[string]*proto.RoomUserInfo {
	return r.users
}

func (r *Room) GameMessageHandler(session *remote.Session, msg []byte) {
	user, ok := r.users[session.Msg.Uid]
	if !ok {
		return
	}
	r.gameFrame.GameMessageHandle(user, session, msg)
}

func (r *Room) GetId() string {
	return r.Id
}

func NewRoom(id string, unionId int64, gameRule *proto.GameRule, u base.UnionBase) *Room {
	room := &Room{
		Id:            id,
		unionId:       unionId,
		gameRule:      gameRule,
		users:         make(map[string]*proto.RoomUserInfo),
		kickSchedules: make(map[string]*time.Timer),
		union:         u,
	}
	// 拼三张
	if gameRule.GameType == int(proto.PinSanZhang) {
		room.gameFrame = sz.NewGameFrame(room, gameRule)
	}
	return room
}
